// scaup - Scala up!
// (c) 2009, Normen Müller, normen.mueller@gmail.com  
// $Id: Parser.scala 86 2010-05-11 21:32:13Z normen.mueller $
package locutor.g.parsing

import java.io.File
import java.net.URL
import java.text.MessageFormat.{format}

import collection.mutable.HashSet

import scaup.Views._

import scalaxx.path._
import scalaxx.parsing.path.XPathLocationPathParser.{parse}

import locutor.g._
import locutor.util.parsing._

/** A dependency graph parser.
 * 
 *  @author <a href="mailto:normen.mueller@googlemail.com">Normen M&#xFC;ller</a>
 *  @version 0.0.4
 * 
 *  @todo improve error handling with ``failure'' and ``err''
 */
private [g] class Parser extends BaseParser {
  /* Fill in abstract defs
   */
  type Tokens = Lexer
  
  val lexical: Tokens = new Lexer
  
  /* Configure lexical parsing
   */  
  lexical.reserved ++= List("graph", "new", "set") ++ BaseParser.reserved
  
  lexical.delimiters ++= List("(", ")", "{", "}", "-", "->", "<", ">", "@", "$", "#") ++ BaseParser.delimiters
  
  /* Define the grammar
   */  
  lazy val dg = ("new" ~ "graph" ~ text ~ text) ~> (nodes >> edges) ^^ {case ((nodes, edges)) => new DependencyGraph(nodes, edges)}
  
  lazy val ig = ("new" ~ "graph" ~ text ~ text) ~> (nodes >> edges) ^^ {case ((nodes, edges)) => new ImpactGraph(nodes, edges)}
  
  private lazy val nodes = ((("new" ~ ":") ~> (text ~ constructor))*) ^^ {
    case nodes =>
      for(node <- nodes) yield node match {
        case typ ~ ((label, url, path, attrs)) => Infom(label, typ, url, path, attrs)
      }
  }
  
  private lazy val constructor = ((("(" ~ "$" ~ "=") ~> text) <~ ",") ~ ((attribute <~ opt(","))*) <~ ")" ^^ {
    case label ~ attrs => 
      //@todo this looks realy awkward
      val u = attrs find {case StringAttribute(key,_) if key == "url" => true case _ => false}
      val p = attrs find {case StringAttribute(key,_) if key == "path" => true case _ => false}
      (label,
       u match {case Some(u: StringAttribute) => Some(u.value.toURL) case _ => None},
       p match {case Some(p: StringAttribute) => Some(parse(p.value)) case _ => None},
       u match {case Some(u) => p match {case Some(p) => attrs - u - p case None => attrs - u} case None => p match {case Some(p) => attrs - p case None => attrs}}
      )
  }
  
  private lazy val attribute: Parser[Attribute] = 
    ( (ident <~ ("=" ~ "set" ~ "<")) ~ (primitives <~ ">") ~ ("{" ~> (((string | numeric) <~ opt(","))*) <~ "}") ^^ {case key ~ typ ~ vals => SetAttribute(key, typ, vals.toSet)}
    | (ident <~ "=") ~ string                                                                                    ^^ {case key ~ value => StringAttribute(key, value)}
    | ident ~ (opt("=" ~> numeric))                                                                              ^^ {case key ~ value => SimpleAttribute(key, value)}
    )
  
  private lazy val edges: (List[Infom] => Parser[(List[Infom], List[Link])]) = (nodes: List[Infom]) =>
    ( ((("new" ~> node_ref) ~ ("-" ~> linktyp <~ "->") ~ node_ref)*) ^^ {
      case edges => 
        (nodes, for(edge <- edges) yield edge match {
          case Left((surl, spath)) ~ ((typ, attrs)) ~ Right(tlabel)       =>            
            val src = (nodes find (_ match {case Infom(_, _, Some(url), Some(path), _) if url == surl && path == spath => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            val tgt = (nodes find (_ match {case Infom(label, _, _, _, _) if label == tlabel => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            Link(src, typ, tgt, attrs)
          case Right(slabel) ~ ((typ, attrs)) ~ Left((turl, tpath))       =>
            val src = (nodes find (_ match {case Infom(label, _, _, _, _) if label == slabel => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            val tgt = (nodes find (_ match {case Infom(_, _, Some(url), Some(path), _) if url == turl && path == tpath => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            Link(src, typ, tgt, attrs)
          case Right(slabel) ~ ((typ, attrs)) ~ Right(tlabel)             =>
            val src = (nodes find (_ match {case Infom(label, _, _, _, _) if label == slabel => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            val tgt = (nodes find (_ match {case Infom(label, _, _, _, _) if label == tlabel => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            Link(src, typ, tgt, attrs)
          case Left((surl, spath)) ~ ((typ, attrs)) ~ Left((turl, tpath)) =>
            val src = (nodes find (_ match {case Infom(_, _, Some(url), Some(path), _) if url == surl && path == spath => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            val tgt = (nodes find (_ match {case Infom(_, _, Some(url), Some(path), _) if url == turl && path == tpath => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            Link(src, typ, tgt, attrs)
        })
    }
    | ((("new" ~> node_ref) ~ ("<-" ~> linktyp <~ "-") ~ node_ref)*) ^^ {
      case edges => 
        (nodes, for(edge <- edges) yield edge match {
          case Left((turl, tpath)) ~ ((typ, attrs)) ~ Right(slabel) =>
            val src = (nodes find (_ match {case Infom(label, _, _, _, _) if label == slabel => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            val tgt = (nodes find (_ match {case Infom(_, _, Some(url), Some(path), _) if url == turl && path == tpath => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            Link(src, typ, tgt, attrs)
          case Right(tlabel) ~ ((typ, attrs)) ~ Left((surl, spath)) =>
            val src = (nodes find (_ match {case Infom(_, _, Some(url), Some(path), _) if url == surl && path == spath => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            val tgt = (nodes find (_ match {case Infom(label, _, _, _, _) if label == tlabel => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            Link(src, typ, tgt, attrs)
          case Right(tlabel) ~ ((typ, attrs)) ~ Right(slabel) =>
            val src = (nodes find (_ match {case Infom(label, _, _, _, _) if label == slabel => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            val tgt = (nodes find (_ match {case Infom(label, _, _, _, _) if label == tlabel => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            Link(src, typ, tgt, attrs)
          case Left((turl, tpath)) ~ ((typ, attrs)) ~ Left((surl, spath)) =>
            val src = (nodes find (_ match {case Infom(_, _, Some(url), Some(path), _) if url == surl && path == spath => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            val tgt = (nodes find (_ match {case Infom(_, _, Some(url), Some(path), _) if url == turl && path == tpath => true case _ => false})).getOrElse(error("Invalid dependency graph link."))
            Link(src, typ, tgt, attrs)
        })
    }
    )
  
  private lazy val node_ref: Parser[Either[(URL, LocationPath), String]] = ("@" ~ "(") ~> text <~ ")" ^^ {
    case label => (label indexOf "|") match {
      case -1     => Right(label)
      case index  => 
        val (url, path) = label splitAt index
        Left((url.toURL, parse(path)))
    }      
  }
  
  private lazy val linktyp = (":" ~> text) ~ linkparams ^^ {case typ ~ attrs => (typ, attrs)}
  
  private lazy val linkparams = ("(" ~ "$" ~ "=" ~ text) ~> (("," ~> attribute)*) <~ ")"
}
